﻿using System.Reactive;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using AntDesign;
using Blazor.CoffeeTalesApi.Entity;
using Blazor.Core;
using Blazor.Localization;
using Blazor.Model.Dto;
using Blazor.Model.Entity;
using Blazor.Pages.UpdateSetting.Modals.Whitelist;
using Blazor.Services.Device;
using Blazor.Services.Update;
using Microsoft.Extensions.Localization;
using ReactiveUI;

namespace Blazor.ViewModels.UpdateSetting;

public class WhitelistEntryViewModel : ViewModelBase
{
    private readonly IUpdateService _updateService;
    private readonly IStringLocalizer<UpdateSettingLocalization> _loc;
    private readonly IDeviceService _deviceService;

    private readonly Channel _currentChannel ;

    private readonly uint _currentNativeVersion;

    private readonly IWhitelistKey _key;
    
    public IWhitelistKey Key => _key;

    private string _raw = string.Empty;

    public string Raw
    {
        get => _raw;
        private set => this.RaiseAndSetIfChanged(ref _raw, value);
    }

    private string _default = string.Empty;

    public string Default
    {
        get => _default;
        private set => this.RaiseAndSetIfChanged(ref _default, value);
    }

    private string _comment = string.Empty;

    public string Comment
    {
        get => _comment;
        private set => this.RaiseAndSetIfChanged(ref _comment, value);
    }

    public ReactiveCommand<string, bool> UpdateRaw { get; }
    public ReactiveCommand<string, bool> UpdateDefault { get; }
    public ReactiveCommand<string, bool> UpdateComment { get; }
    public ReactiveCommand<WhitelistOptions.EditType, Unit> Update { get; }

    public ReactiveCommand<Unit, string> RefreshToolTip { get; }

    private readonly ObservableAsPropertyHelper<bool> _isUpdating;
    public bool IsUpdating => _isUpdating.Value;
    
    public Interaction<WhitelistOptions, WhitelistResult?> ModifyResourceVersionEntryInteraction { get; } = new();

    public ReactiveCommand<Unit, bool> Delete { get; }
    
    public Interaction<Unit, bool> ConfirmDeleteInteraction { get; } = new();

    public ReactiveCommand<Unit, KeyValuePair<IWhitelistKey, ResourceVersionEntry>?> Copy { get; }

    public ReactiveCommand<Unit, ResourceVersionEntry?> Apply { get; }
    
    public Interaction<Unit, bool> ConfirmApplyInteraction { get; } = new();

    private readonly ObservableAsPropertyHelper<string> _keyTip;
    public string KeyTip => _keyTip.Value;

    public WhitelistEntryViewModel(IWhitelistKey key, ResourceVersionEntry entry, IUpdateService updateService, IStringLocalizer<UpdateSettingLocalization> loc, IDeviceService deviceService, Channel currentChannel, uint currentNativeVersion)
    {
        _key = key;
        _updateService = updateService;
        _loc = loc;
        _deviceService = deviceService;
        _currentChannel = currentChannel;
        _currentNativeVersion = currentNativeVersion;

        Raw = entry.Raw;
        Default = entry.Default;
        Comment = entry.Comment;

        var canExecute = new BehaviorSubject<bool>(false);

        UpdateRaw = ReactiveCommand.CreateFromTask<string, bool>(UpdateRawImpl, canExecute);
        UpdateDefault = ReactiveCommand.CreateFromTask<string, bool>(UpdateDefaultImpl, canExecute);
        UpdateComment = ReactiveCommand.CreateFromTask<string, bool>(UpdateCommentImpl, canExecute);
        Update = ReactiveCommand.CreateFromTask<WhitelistOptions.EditType>(UpdateImpl, canExecute);
        Delete = ReactiveCommand.CreateFromTask(DeleteImpl, canExecute);
        Copy = ReactiveCommand.CreateFromTask(CopyImpl, canExecute);
        Apply = ReactiveCommand.CreateFromTask(ApplyImpl, canExecute);
        RefreshToolTip = ReactiveCommand.CreateFromTask(RefreshToolTipImpl);
        
        var anyIsExecuting = Observable.CombineLatest(
            UpdateRaw.IsExecuting,
            UpdateDefault.IsExecuting,
            UpdateComment.IsExecuting,
            Update.IsExecuting,
            Delete.IsExecuting,
            Copy.IsExecuting,
            Apply.IsExecuting,
            (a, b, c, d, e, f, g) => a || b || c || d || e || f || g
        );

        anyIsExecuting.Subscribe(b => canExecute.OnNext(!b));
        anyIsExecuting.ToProperty(this, x => x.IsUpdating, out _isUpdating);

        RefreshToolTip.ToProperty(this, x => x.KeyTip, out _keyTip);
    }

    private async Task<bool> UpdateRawImpl(string newRaw)
    {
        if (newRaw == Raw)
        {
            return false;
        }

        var success = await _updateService.RemoveWhitelistAsync(_currentChannel, _currentNativeVersion, _key);
        if (!success)
        {
            throw new Exception("Failed to remove whitelist");
        }

        success = await _updateService.AddWhitelistAsync(_currentChannel, _currentNativeVersion, Key, new ResourceVersionEntry()
        {
            Raw = newRaw,
            Default = Default,
            Comment = Comment,
        });
        if (!success)
        {
            return false;
        }

        Raw = newRaw;
        return true;
    }

    private async Task<bool> UpdateDefaultImpl(string newDefault)
    {
        if (newDefault == Default)
        {
            return false;
        }

        var success = await _updateService.RemoveWhitelistAsync(_currentChannel, _currentNativeVersion, _key);
        if (!success)
        {
            throw new Exception("Failed to remove whitelist");
        }

        success = await _updateService.AddWhitelistAsync(_currentChannel, _currentNativeVersion, Key, new ResourceVersionEntry()
        {
            Raw = Raw,
            Default = newDefault,
            Comment = Comment,
        });
        if (!success)
        {
            return false;
        }

        Default = newDefault;

        return true;
    }

    private async Task<bool> UpdateCommentImpl(string newComment)
    {
        if (newComment == Comment)
        {
            return false;
        }

        var success = await _updateService.RemoveWhitelistAsync(_currentChannel, _currentNativeVersion, _key);
        if (!success)
        {
            throw new Exception("Failed to remove whitelist");
        }

        success = await _updateService.AddWhitelistAsync(_currentChannel, _currentNativeVersion, Key, new ResourceVersionEntry()
        {
            Raw = Raw,
            Default = Default,
            Comment = newComment,
        });
        if (!success)
        {
            return false;
        }

        Comment = newComment;

        return true;
    }

    private async Task UpdateImpl(WhitelistOptions.EditType  editType)
    {
        var result = await ModifyResourceVersionEntryInteraction.Handle(new WhitelistOptions()
        {
            Key = Key,
            Raw = Raw,
            Default = Default,
            Comment = Comment,
            Type = editType,
        });

        if (result is null)
        {
            return;
        }
        
        var success = await _updateService.RemoveWhitelistAsync(_currentChannel, _currentNativeVersion, _key);
        if (!success)
        {
            throw new Exception("Failed to remove whitelist");
        }

        var newKey = result.Value.Key;
        var newEntry = result.Value.Entry;
        success = await _updateService.AddWhitelistAsync(_currentChannel, _currentNativeVersion, newKey, newEntry);
        if (!success)
        {
            throw new Exception("Failed to add whitelist");
        }
        
        Raw = newEntry.Raw;
        Default = newEntry.Default;
        Comment = newEntry.Comment;
    }

    private async Task<bool> DeleteImpl()
    {
        var confirmed = await ConfirmDeleteInteraction.Handle(new Unit());
        if (!confirmed)
        {
            return false;
        }
        
        var success = await _updateService.RemoveWhitelistAsync(_currentChannel, _currentNativeVersion, _key);
        if (!success)
        {
            throw new Exception("Failed to remove whitelist");
        }

        return true;
    }

    private async Task<KeyValuePair<IWhitelistKey, ResourceVersionEntry>?> CopyImpl()
    {
        var result = await ModifyResourceVersionEntryInteraction.Handle(new WhitelistOptions()
        {
            Key = Key,
            Raw = Raw,
            Default = Default,
            Comment = Comment,
            Type = WhitelistOptions.EditType.Key,
        });

        if (result is null)
        {
            return null;
        }

        var newKey = result.Value.Key;
        var entry = result.Value.Entry;
        if (newKey.Equals(_key))
        {
            return null;
        }
        
        if (!await _updateService.AddWhitelistAsync(_currentChannel, _currentNativeVersion, newKey, entry))
        {
            throw new Exception("Failed to add whitelist");
        }
        
        return new KeyValuePair<IWhitelistKey, ResourceVersionEntry>(newKey, entry);
    }

    private async Task<ResourceVersionEntry?> ApplyImpl()
    {
        var confirmed = await ConfirmApplyInteraction.Handle(new Unit());

        if (!confirmed)
        {
            return null;
        }
        
        var newResourceVersion = new ResourceVersionEntry()
        {
            Raw = Raw,
            Default = Default,
            Comment = Comment
        };
        var success = await _updateService.SetResourceVersionAsync(_currentChannel, _currentNativeVersion, newResourceVersion);
        if (!success)
        {
            throw new Exception("Failed to set resource version");
        }

        return newResourceVersion;
    }

    private async Task<string> RefreshToolTipImpl()
    {
        switch (Key)
        {
            case IpKey ipKey:
                var myIp = await _updateService.FetchMyIpAsync();
                if (Equals(myIp, ipKey.IP))
                {
                    return _loc[UpdateSettingLocalization.IsMyIP];
                }

                return _loc[UpdateSettingLocalization.NotMyIP];
            case DeviceIdKey deviceIdKey:
                var s = deviceIdKey.ToString();
                if (!Guid.TryParse(s, out var id))
                {
                    return string.Empty;
                }

                var desc = await _deviceService.GetDescription(id);
                return desc ?? string.Empty;
            default:
                throw new ArgumentOutOfRangeException(nameof(Key));
        }
    }
}